package org.je.database.impl;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.je.database.DataSet;
import org.je.database.DataTable;

/**
 * @author fanyifeng
 */
public class SqlExecutor
{
	private final DBConnection	dbc;

	public SqlExecutor(DBConnection dbc) {
		if (dbc == null) {
			throw new NullPointerException("dbconnection-is-null");
		}
		this.dbc = dbc;
	}

	/**
	 * @param sqls
	 * @return dataSet
	 */
	public synchronized DataSet transact(String... sqls) {
		List<String> list = new ArrayList<String>(sqls.length);
		for (String s : sqls) {
			list.add(s);
		}
		return transact(list);
	}

	/**
	 * @param sqls
	 * @return dataset
	 */
	public synchronized DataSet transact(List<String> sqls) {
		if (sqls == null) {
			throw new NullPointerException("sqls-is-null");
		}
		Object o = trans1(sqls, true);
		if (o instanceof DataSet) {
			return (DataSet) o;
		}
		try {
			dbc.reconnect();
		} catch (Exception ex) {
			throw (TransactionException) o;
		}
		return (DataSet) trans1(sqls, false);
	}

	/**
	 * @param sql
	 * @return datatable
	 */
	public synchronized DataTable query(String sql) {
		Object obj = query1(sql, true);
		if (obj instanceof DataTable)
			return (DataTable) obj;
		try {
			dbc.reconnect();
		} catch (Exception ex) {
			throw (RuntimeException) obj;
		}
		return (DataTable) query1(sql, false);

	}

	/**
	 * @param sql
	 * @return update count
	 */
	public synchronized int update(String sql) {
		Object obj = update0(sql, true);
		if (obj instanceof Integer)
			return (Integer) obj;
		try {
			dbc.reconnect();
		} catch (Exception ex) {
			throw (RuntimeException) obj;
		}
		return (Integer) update0(sql, false);
	}

	/**
	 * private methods....
	 */

	private Object trans1(List<String> sqls, boolean testWhenError) {
		try {
			long time = 0;
			if (dbc.ds.showSql) {
				time = System.nanoTime();
			}
			DataSet set = trans0(sqls);
			dbc.commit();
			if (dbc.ds.showSql) {
				time = (System.nanoTime() - time) / 1000;
				info("# trans-sqls use " + time + " micro second\r\n" + transSqlString(sqls) + "# result #");
			}
			if (dbc.ds.showResult) {
				info(transResultString(set));
			}
			return set;
		} catch (TransactionException ex) {
			dbc.rollback();
			if (testWhenError == false || testSql() == true) {
				throw ex;
			}
			return ex;
		}
	}

	private DataSet trans0(List<String> sqls) {
		DataSet set = new DataSet();
		int i = 0;
		try {
			for (; i < sqls.size(); ++i) {
				String sql = sqls.get(i);

				if (SqlChecker.isSelect(sql)) {
					Object o = query0(sql);
					set.putSqlResultInSet(o);
				} else {
					Object o = dbc.statement().executeUpdate(sql);
					set.putSqlResultInSet(o);
				}
			}
		} catch (Exception ex) {
			throw new TransactionException("trans-sql-error:" + sqls.get(i), ex);
		}
		return set;
	}

	private Object query1(String sql, boolean testWhenError) {
		try {
			long startTime = 0;
			if (dbc.ds.showSql) {
				startTime = System.nanoTime();
			}
			DataTable tb = query0(sql);
			dbc.commit();
			if (dbc.ds.showSql) {
				info("# " + sql + " # use " + (System.nanoTime() - startTime) / 1000 + " micro second");
			}
			if (dbc.ds.showResult) {
				info("# result size : " + tb.getRowCount() + " #\r\n" + tb.toShowSting(dbc.ds.maxShowCount));
			}
			return tb;
		} catch (Exception ex) {
			dbc.rollback();
			if (testWhenError == false || testSql() == true) {
				throw new RuntimeException(sql, ex);
			}
			return new RuntimeException(sql, ex);
		}
	}

	private DataTable query0(String sql) throws SQLException {
		ResultSet rs = dbc.statement().executeQuery(sql);
		ResultSetMetaData meta = rs.getMetaData();
		String[] metaData = new String[meta.getColumnCount()];
		for (int i = 0; i < metaData.length; ++i) {
			metaData[i] = meta.getColumnName(i + 1);
		}
		DataTable tb = new DataTable(metaData);
		while (rs.next()) {
			Object[] arr = new Object[metaData.length];
			for (int i = 0; i < arr.length; ++i) {
				arr[i] = rs.getObject(i + 1);
			}
			tb.putRowData(arr);
		}
		closeSet(rs);
		return tb;
	}

	private Object update0(String sql, boolean testWhenError) {
		try {
			long startTime = 0;
			if (dbc.ds.showSql) {
				startTime = System.nanoTime();
			}
			int count = dbc.statement().executeUpdate(sql);
			dbc.commit();
			if (dbc.ds.showSql) {
				info("# " + sql + " > " + count + " # use " + (System.nanoTime() - startTime) / 1000 + " micro second");
			}
			return count;
		} catch (Exception ex) {
			dbc.rollback();
			if (testWhenError == false || testSql() == true) {
				throw new RuntimeException(sql, ex);
			}
			return new RuntimeException(sql, ex);
		}
	}

	private boolean testSql() {
		try {
			info("testSql : " + dbc.ds.testSql);
			ResultSet set = dbc.statement().executeQuery(dbc.ds.testSql);
			closeSet(set);
			dbc.commit();
			return true;
		} catch (Exception ex) {
			dbc.rollback();
			return false;
		}
	}

	private void closeSet(ResultSet set) {
		if (set != null) {
			try {
				set.close();
			} catch (Exception e) {}
		}
	}

	/** 调试辅助方法 **/

	private void info(Object o) {
		System.out.println(o == null ? "null" : o);
	}

	private String transSqlString(List<String> sqls) {
		StringBuilder bf = new StringBuilder();
		for (int i = 0; i < sqls.size() && i < dbc.ds.maxShowCount; ++i) {
			bf.append("[" + i + "] " + sqls.get(i) + "\r\n");
		}
		return bf.toString();
	}

	private String transResultString(DataSet set) {
		StringBuilder bf = new StringBuilder();
		for (int i = 0; i < set.size() & i < dbc.ds.maxShowCount; ++i) {
			Object o = set.get(i);
			if (o instanceof Integer) {
				bf.append("[" + i + "] 影响行数" + o + "\r\n");
			} else {
				bf.append("[" + i + "] is datatable\r\n");
			}
		}
		return bf.toString();
	}
}
